<?php
/**
 * 應用程序類，負責調度框架
 * 
 * @author tom.wang<tom.wang.sz@qq.com>
 */
class App extends AbsCatBase {
	private static $actionName;
	private static $methodName;
	private static $actionObj;
	private static $filterObjArr;
	private static $errorNum = 0;
	private static $exceptionNum = 0;
	/**
	 * 處理入口方法
	 *
	 * @param array $cfg
	 * @return void
	 */
	public static function run($cfg = array()) {
		self::setCfg($cfg);
		self::init();
		self::import();
		self::exec();
		self::finalize();
	}
	
	/**
	 * get app info
	 */
	public static function info() {
		return array(
			'errorNum' => self::$errorNum,
			'exceptionNum' => self::$exceptionNum,
			'actionName' => self::$actionName,
			'methodName' => self::$methodName,
		);
	}
	
	/**
	 * 初始化全站配置（包括app、db、filter）
	 *
	 * @param array $cfg
	 * @return void
	 */
	private static function setCfg($cfg) {
		ConfigUtil::initCfg($cfg);
	}
	
	private static function init() {
		if(!isPhp()) throwException('php5.0.0 needed');
		set_error_handler(array(__CLASS__, 'errorHandler'), E_ALL);
		set_exception_handler(array(__CLASS__,'exceptionHandler'));
		
		# 设置错误处理
		# 在现网环境，应该同时将php.ini中的display_errors指令关闭，这个指令可以用ini_set在运行时设置，但是遇到Fatal error却不起作用（错误发生时还没生效）
		error_reporting(AC('app_debug') ? (E_ALL ^ E_NOTICE) : 0);
		
		# 销毁以下全局变量
		unset($HTTP_ENV_VARS, $HTTP_POST_VARS, $HTTP_GET_VARS, $HTTP_POST_FILES, $HTTP_COOKIE_VARS, $HTTP_SESSION_VARS);
		
		# 如果register_globals打开，则销毁相关全局变量
		if(ini_get('register_globals') == 1) {
			$_g_var_arr = array(
				'G'    => $_GET,
				'P'    => $_POST,
				'C'    => $_COOKIE,
				'E'    => $_ENV,
				'S'    => $_SERVER,
			);
			$variables_order = ini_get('variables_order');
			foreach($_g_var_arr as $k => $v) {
				if(strpos($variables_order, $k) !== FALSE) {
					foreach($v as $v_k => $v_v) {
						unset($GLOBALS[$v_k]);
					}
				}
			}
		}
		
		# 设置内容输出缓冲
		if(AC('app_gzip')) {
			ob_start('ob_gzhandler');
		} else {
			ob_start();
		}
		
		# 本地化设置
		if(function_exists('setlocale')) {
			setlocale(LC_ALL, AC('app_locale'));
		}
		# 对magic_quotes_gpc进行处理
		if(function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc() == 1) {
			$_GET = stripslashesDeep($_GET);
			$_POST = stripslashesDeep($_POST);
			$_COOKIE = stripslashesDeep($_COOKIE);
			$_REQUEST = stripslashesDeep($_REQUEST); //这个也需要，因为它和前三个没有引用关系
		}
		# 取消magic_quotes_runtime
		if(get_magic_quotes_runtime() == 1 && function_exists('set_magic_quotes_runtime')) {
			set_magic_quotes_runtime(0);
		}
		# 取消magic_quotes_sybase设置
		if(@ini_get('magic_quotes_sybase') != 0) {
			@ini_set('magic_quotes_sybase', 0);
		}
		# 设置时区
		# [date_default_timezone_set是5.1版本开始提供的，使用function_exists判断更有扩展性]
		if(function_exists('date_default_timezone_set')) {
			date_default_timezone_set(AC('app_timezone'));
		}
	}
	
	/**
	 * check and import classes
	 */
	private static function import() {
		# 导入db类
		if(true === DC('db_enable')) {
			require_once FRAMEWORK_CAT_ROOT . 'Lib/Db/Db.class.php';
			require_once FRAMEWORK_CAT_ROOT . 'Lib/Db/IDb.iface.php';
		}
		
		# 导入action类
		$actionName = getActionName();
		$actionClz = ucfirst($actionName) . 'Action';
		$actionClzFile = '';
		if(file_exists(AC('app_action_path') . "{$actionClz}.class.php")) {
			$actionClzFile = AC('app_action_path') . "{$actionClz}.class.php";
		} elseif(file_exists(FRAMEWORK_ACTION_ROOT . "{$actionClz}.class.php")) {
			$actionClzFile = FRAMEWORK_ACTION_ROOT . "{$actionClz}.class.php";
		} else {
			$actionClzFile = FRAMEWORK_ACTION_ROOT . "CatAction.class.php";
			$actionClz = 'CatAction';
		}
		if(!file_exists($actionClzFile)) throwException("Action doesn't exist : {$actionClzFile}");
		require_once $actionClzFile;
		# 初始化action
		self::$actionObj = new $actionClz();
		
		# 导入当前action的filter类
		$filters = FC();
		foreach($filters as $filterName => $filterAction) {
			$filterActionArr = explode(',', $filterAction);
			array_walk($filterActionArr, 'trim');
			if(!in_array('all', $filterActionArr) && !in_array($actionName, $filterActionArr)) unset($filters[$filterName]);
		}
		
		foreach($filters as $filterName => $filterAction) {
			$filterClz = ucfirst(strtolower(substr(trim($filterName), 7))) . 'Filter';
			$filterClzFile = file_exists(AC('app_filter_path') . "{$filterClz}.class.php") ? AC('app_filter_path') . "{$filterClz}.class.php" : FRAMEWORK_FILTER_ROOT . "{$filterClz}.class.php";
			if(!file_exists($filterClzFile)) throwException("Filter doesn't exist : {$filterClzFile}");
			require_once $filterClzFile;
			# 初始化filter
			self::$filterObjArr[] = new $filterClz();
		}
	}
	
	private static function exec() {
		$method = getMethodName();
		$actionObj = self::$actionObj;
		# 调用filter的before方法
		foreach(self::$filterObjArr as $filterObj) {
			# 這裡的$action需要引用
			call_user_func(array($filterObj, 'before'), $actionObj);
		}
		# 调用_before_$method方法
		if(method_exists($actionObj, "_before_{$method}")) call_user_func(array($actionObj, "_before_{$method}"));
		# 调用$method方法
		if(method_exists($actionObj, $method)) call_user_func(array($actionObj, $method));
		# 调用_after_$method方法
		if(method_exists($actionObj, "_after_{$method}")) call_user_func(array($actionObj, "_after_{$method}"));
		# 调用filter的after方法
		foreach(self::$filterObjArr as $filterObj) {
			call_user_func(array($filterObj, 'after'), $actionObj);
		}
	}
	
	private static function finalize() {
		# 其实这个方法可以不用，当网站使用frameset的时候，最好使用，见手册页
		session_write_close();
	}
	
	/**
	 * 错误处理函数
	 *
	 * 须设置为public，否则外部的错误捕获不到
	 * 只能捕捉到E_USER类型的错误
	 * 如果return false，则错误信息会继续上升，最后成为Fatal Error，程序停止执行
	 */
	public static function errorHandler($errno, $errstr, $errfile, $errline, $errcontext) {
		++self::$errorNum;
		# log to file
		# from php5.2.0, The error handler must return FALSE to populate $php_errormsg
		// 将error转为异常
		throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
	}
	
	/**
	 * 异常处理函数
	 * 
	 * 须设置为public，否则外部的异常捕获不到
	 * 当一个异常没有被catch到，会在这里处理，该方法处理完毕后，程序停止执行
	 * 异常处理和错误控制是分开的，try...catch捕获不到错误，可以将错误转为异常。如errorHandler方法
	 */
	public static function exceptionHandler($ex) {
		++self::$exceptionNum;
		var_dump($ex);
		# log to file
	}
}
?>